
/*-------------------------------------------------------------------------
 *
 * exec.c
 *		查找和验证可执行文件的函数
 *
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/common/exec.c
 *
 *-------------------------------------------------------------------------
 */

#ifndef FRONTEND
#include "postgres.h"
#else
#include "postgres_fe.h"
#endif

#include <signal.h>
#include <sys/stat.h>
#include <sys/wait.h>
#include <unistd.h>

#ifdef EXEC_BACKEND
#if defined(HAVE_SYS_PERSONALITY_H)
#include <sys/personality.h>
#elif defined(HAVE_SYS_PROCCTL_H)
#include <sys/procctl.h>
#endif
#endif

/* 禁用 mingw CRT 的命令行参数自动扩展 */
#if defined(WIN32) && !defined(_MSC_VER)
extern int	_CRT_glob = 0;		/* 0 关闭扩展；1 打开扩展 */
#endif

/*
 * 一个黑客解决方案，允许在一次宏调用中表达前端和后端的错误报告。
 * log_error 的第一个参数是某种类型的 errcode() 调用（如果是 FRONTEND 则被忽略）；
 * 其余是 errmsg_internal() 的参数，即消息字符串及其任何参数。
 *
 * 调用者必须为消息字符串提供 gettext 包装器（如果适用），以便在 FRONTEND 情况下进行翻译；
 * 这促使使用 errmsg_internal() 而不是 errmsg()。我们在宏内部处理换行符的附加（如果需要），
 * 以便每次调用只有一个可翻译字符串，而不是两个。
 */
#ifndef FRONTEND
#define log_error(errcodefn, ...) \
	ereport(LOG, (errcodefn, errmsg_internal(__VA_ARGS__)))
#else
#define log_error(errcodefn, ...) \
	(fprintf(stderr, __VA_ARGS__), fputc('\n', stderr))
#endif

#ifdef _MSC_VER
#define getcwd(cwd,len)  GetCurrentDirectory(len, cwd)
#endif

static int	fc_resolve_symlinks(char *fc_path);

#ifdef WIN32
static BOOL GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser);
#endif

/*
 * validate_exec -- 验证 "path" 是否为可执行文件
 *
 * 如果找到文件且未遇到错误，则返回 0。
 *		  -1 表示常规文件 "path" 不存在或无法执行。
 *		  -2 表示文件有效但无法读取。
 */
int validate_exec(const char *fc_path)
{
	struct stat fc_buf;
	int			fc_is_r;
	int			fc_is_x;

#ifdef WIN32
	char		path_exe[MAXPGPATH + sizeof(".exe") - 1];

	/* Win32 对 stat() 要求后缀为 .exe */
	if (strlen(path) >= strlen(".exe") &&
		pg_strcasecmp(path + strlen(path) - strlen(".exe"), ".exe") != 0)
	{
		strlcpy(path_exe, path, sizeof(path_exe) - 4);
		strcat(path_exe, ".exe");
		path = path_exe;
	}
#endif

	/*
	 * 确保文件存在且是常规文件。
	 *
	 * XXX 如果你有一个损坏的系统，stat() 查看符号链接而不是底层文件，则会出错。
	 */
	if (stat(fc_path, &fc_buf) < 0)
		return -1;

	if (!S_ISREG(fc_buf.st_mode))
		return -1;

	/*
	 * 确保文件可执行且可读（动态加载所需）。
	 */
#ifndef WIN32
	fc_is_r = (access(fc_path, R_OK) == 0);
	fc_is_x = (access(fc_path, X_OK) == 0);
#else
	fc_is_r = buf.st_mode & S_IRUSR;
	fc_is_x = buf.st_mode & S_IXUSR;
#endif
	return fc_is_x ? (fc_is_r ? 0 : -2) : -1;
}


/*
 * find_my_exec -- 查找有效可执行文件的绝对路径
 *
 *	argv0 是命令行中传递的名称
 *	retpath 是输出区域（必须为 MAXPGPATH 大小）
 *	成功返回 0，出错返回 -1。
 *
 * 我们必须如此努力寻找绝对路径的原因是，在某些平台上，
 * 如果不知道可执行文件的位置，我们无法进行动态加载。
 * 此外，我们需要的是绝对路径而不是相对路径，因为稍后我们会更改工作目录。
 * 最后，我们希望得到一个真实路径，而不是符号链接位置，
 * 以便相对于可执行文件找到我们安装的其他文件。
 */
int find_my_exec(const char *fc_argv0, char *fc_retpath)
{
	char		fc_cwd[MAXPGPATH],
				fc_test_path[MAXPGPATH];
	char	   *fc_path;

	if (!getcwd(fc_cwd, MAXPGPATH))
	{
		log_error(errcode_for_file_access(),
				  _("could not identify current directory: %m"));
		return -1;
	}

	/*
	 * 如果 argv0 包含分隔符，则未使用 PATH。
	 */
	if (first_dir_separator(fc_argv0) != NULL)
	{
		if (is_absolute_path(fc_argv0))
			strlcpy(fc_retpath, fc_argv0, MAXPGPATH);
		else
			join_path_components(fc_retpath, fc_cwd, fc_argv0);
		canonicalize_path(fc_retpath);

		if (validate_exec(fc_retpath) == 0)
			return fc_resolve_symlinks(fc_retpath);

		log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE),
				  _("invalid binary \"%s\""), fc_retpath);
		return -1;
	}

#ifdef WIN32
	/* Win32 首先检查当前目录中的无斜杠名称 */
	join_path_components(retpath, cwd, argv0);
	if (validate_exec(retpath) == 0)
		return resolve_symlinks(retpath);
#endif

	/*
	 * 由于未提供显式路径，用户必须依赖 PATH。
	 * 我们将搜索相同的 PATH。
	 */
	if ((fc_path = getenv("PATH")) && *fc_path)
	{
		char	   *fc_startp = NULL,
				   *fc_endp = NULL;

		do
		{
			if (!fc_startp)
				fc_startp = fc_path;
			else
				fc_startp = fc_endp + 1;

			fc_endp = first_path_var_separator(fc_startp);
			if (!fc_endp)
				fc_endp = fc_startp + strlen(fc_startp); /* 指向结束 */

			strlcpy(fc_test_path, fc_startp, Min(fc_endp - fc_startp + 1, MAXPGPATH));

			if (is_absolute_path(fc_test_path))
				join_path_components(fc_retpath, fc_test_path, fc_argv0);
			else
			{
				join_path_components(fc_retpath, fc_cwd, fc_test_path);
				join_path_components(fc_retpath, fc_retpath, fc_argv0);
			}
			canonicalize_path(fc_retpath);

			switch (validate_exec(fc_retpath))
			{
				case 0:			/* 找到正常 */
					return fc_resolve_symlinks(fc_retpath);
				case -1:		/* 甚至连候选都不是，继续寻找 */
					break;
				case -2:		/* 找到但不合格 */
					log_error(errcode(ERRCODE_WRONG_OBJECT_TYPE),
							  _("could not read binary \"%s\""),
							  fc_retpath);
					break;
			}
		} while (*fc_endp);
	}

	log_error(errcode(ERRCODE_UNDEFINED_FILE),
			  _("could not find a \"%s\" to execute"), fc_argv0);
	return -1;
}


/*
 * resolve_symlinks - 解析符号链接到底层文件
 *
 * 将 "path" 替换为引用文件的绝对路径。
 *
 * 成功返回 0，出错返回 -1。
 *
 * 注意：我们对生成漂亮的错误消息并不特别紧张，
 * 因为我们并不期待此处出现错误；我们只是确定符号链接确实指向有效的可执行文件。
 */
static int fc_resolve_symlinks(char *fc_path)
{
#ifdef HAVE_READLINK
	struct stat fc_buf;
	char		fc_orig_wd[MAXPGPATH],
				fc_link_buf[MAXPGPATH];
	char	   *fc_fname;

	/*
	 * 为了正确解析符号链接，我们必须先切换到它的目录，然后
	 * 切换到符号链接指向的地方；否则我们可能无法正确解析相对链接（考虑涉及挂载点的情况）。
	 * 在跟随最后一个符号链接后，我们使用 getcwd() 来确定我们在哪里。
	 *
	 * 有人可能认为如果路径最初不指向符号链接，我们可以跳过所有这些，
	 * 但这是错误的。我们还想去掉给定路径中存在的任何目录符号链接。
	 * 我们期望 getcwd() 能给我们一个准确、不含符号链接的路径。
	 */
	if (!getcwd(fc_orig_wd, MAXPGPATH))
	{
		log_error(errcode_for_file_access(),
				  _("could not identify current directory: %m"));
		return -1;
	}

	for (;;)
	{
		char	   *fc_lsep;
		int			fc_rllen;

		fc_lsep = last_dir_separator(fc_path);
		if (fc_lsep)
		{
			*fc_lsep = '\0';
			if (chdir(fc_path) == -1)
			{
				log_error(errcode_for_file_access(),
						  _("could not change directory to \"%s\": %m"), fc_path);
				return -1;
			}
			fc_fname = fc_lsep + 1;
		}
		else
			fc_fname = fc_path;

		if (lstat(fc_fname, &fc_buf) < 0 ||
			!S_ISLNK(fc_buf.st_mode))
			break;

		errno = 0;
		fc_rllen = readlink(fc_fname, fc_link_buf, sizeof(fc_link_buf));
		if (fc_rllen < 0 || fc_rllen >= sizeof(fc_link_buf))
		{
			log_error(errcode_for_file_access(),
					  _("could not read symbolic link \"%s\": %m"), fc_fname);
			return -1;
		}
		fc_link_buf[fc_rllen] = '\0';
		strcpy(fc_path, fc_link_buf);
	}

	/* 必须暂时将最终组件从'path'复制出来 */
	strlcpy(fc_link_buf, fc_fname, sizeof(fc_link_buf));

	if (!getcwd(fc_path, MAXPGPATH))
	{
		log_error(errcode_for_file_access(),
				  _("could not identify current directory: %m"));
		return -1;
	}
	join_path_components(fc_path, fc_path, fc_link_buf);
	canonicalize_path(fc_path);

	if (chdir(fc_orig_wd) == -1)
	{
		log_error(errcode_for_file_access(),
				  _("could not change directory to \"%s\": %m"), fc_orig_wd);
		return -1;
	}
#endif							/* HAVE_READLINK */

	return 0;
}


/*
 * 在我们二进制文件的目录中查找另一个程序，
 * 然后确保它是正确的版本。
 */
int find_other_exec(const char *fc_argv0, const char *fc_target,
				const char *fc_versionstr, char *fc_retpath)
{
	char		fc_cmd[MAXPGPATH];
	char		fc_line[MAXPGPATH];

	if (find_my_exec(fc_argv0, fc_retpath) < 0)
		return -1;

	/* 修剪程序名称，只保留目录 */
	*last_dir_separator(fc_retpath) = '\0';
	canonicalize_path(fc_retpath);

	/* 现在附加另一个程序的名称 */
	snprintf(fc_retpath + strlen(fc_retpath), MAXPGPATH - strlen(fc_retpath),
			 "/%s%s", fc_target, EXE);

	if (validate_exec(fc_retpath) != 0)
		return -1;

	snprintf(fc_cmd, sizeof(fc_cmd), "\"%s\" -V", fc_retpath);

	if (!pipe_read_line(fc_cmd, fc_line, sizeof(fc_line)))
		return -1;

	if (strcmp(fc_line, fc_versionstr) != 0)
		return -2;

	return 0;
}


/*
 * 在管道中执行命令并从中读取第一行。
 */
char * pipe_read_line(char *fc_cmd, char *fc_line, int fc_maxsize)
{
	FILE	   *fc_pgver;

	/* 刷新输出缓冲区，以防 popen 不这样做... */
	fflush(stdout);
	fflush(stderr);

	errno = 0;
	if ((fc_pgver = popen(fc_cmd, "r")) == NULL)
	{
		perror("popen failure");
		return NULL;
	}

	errno = 0;
	if (fgets(fc_line, fc_maxsize, fc_pgver) == NULL)
	{
		if (feof(fc_pgver))
			fprintf(stderr, "no data was returned by command \"%s\"\n", fc_cmd);
		else
			perror("fgets failure");
		pclose(fc_pgver);			/* 不进行错误检查 */
		return NULL;
	}

	if (pclose_check(fc_pgver))
		return NULL;

	return fc_line;
}


/*
 * pclose() 以及有用的错误报告
 */
int pclose_check(FILE *fc_stream)
{
	int			fc_exitstatus;
	char	   *fc_reason;

	fc_exitstatus = pclose(fc_stream);

	if (fc_exitstatus == 0)
		return 0;				/* 一切正常 */

	if (fc_exitstatus == -1)
	{
		/* pclose() 本身失败了，并希望设置 errno */
		log_error(errcode(ERRCODE_SYSTEM_ERROR),
				  _("%s() failed: %m"), "pclose");
	}
	else
	{
		fc_reason = wait_result_to_str(fc_exitstatus);
		log_error(errcode(ERRCODE_SYSTEM_ERROR),
				  "%s", fc_reason);
		pfree(fc_reason);
	}
	return fc_exitstatus;
}

/*
 *	set_pglocale_pgservice
 *
 *	设置特定于应用程序的区域设置和服务目录
 *
 *	此函数接受 argv[0] 的值，而不是完整路径。
 *
 * (您可能会想知道为什么这在 exec.c 中。这需要该模块的
 * 服务，并且不会引入新的依赖关系，所以在任何地方看起来都不错。)
 */
void set_pglocale_pgservice(const char *fc_argv0, const char *fc_app)
{
	char		fc_path[MAXPGPATH];
	char		fc_my_exec_path[MAXPGPATH];

	/* 不要在后端设置 LC_ALL */
	if (strcmp(fc_app, PG_TEXTDOMAIN("postgres")) != 0)
	{
		setlocale(LC_ALL, "");

		/*
		 * 可以为这里复现 PostmasterMain() 中关于进程是否为多线程的测试辩护。
		 * 与邮局进程不同，前端程序不调用 sigprocmask() 或以其他方式提供
		 * 信号处理程序之间的互斥。虽然使用 fork() 的前端,
		 * 如果是多线程的，形式上暴露于未定义行为中，我们并未见证具体的错误。
		 * 因此，在这里抱怨多线程可能只是小题大做。
		 */
	}

	if (find_my_exec(fc_argv0, fc_my_exec_path) < 0)
		return;

#ifdef ENABLE_NLS
	get_locale_path(fc_my_exec_path, fc_path);
	bindtextdomain(fc_app, fc_path);
	textdomain(fc_app);
	/* 为 libpq 使用设置，但不要覆盖现有设置 */
	setenv("PGLOCALEDIR", fc_path, 0);
#endif

	if (getenv("PGSYSCONFDIR") == NULL)
	{
		get_etc_path(fc_my_exec_path, fc_path);
		/* 为 libpq 使用设置 */
		setenv("PGSYSCONFDIR", fc_path, 0);
	}
}

#ifdef EXEC_BACKEND
/*
 * 为受益于在 Unix 系统上测试 EXEC_BACKEND 的 PostgreSQL 开发者，
 * （通常仅在 Windows 上执行的代码路径），提供一种
 * 禁用地址空间布局随机化的方法，前提是我们知道在该平台上如何实现。
 * 否则，后端可能无法附加到邮局进程所选择的固定地址的共享内存。
 * （另请参阅 sysv_shmem.c 中的 macOS 特定黑客。）
 */
int pg_disable_aslr(void)
{
#if defined(HAVE_SYS_PERSONALITY_H)
	return personality(ADDR_NO_RANDOMIZE);
#elif defined(HAVE_SYS_PROCCTL_H) && defined(PROC_ASLR_FORCE_DISABLE)
	int			data = PROC_ASLR_FORCE_DISABLE;

	return procctl(P_PID, 0, PROC_ASLR_CTL, &data);
#else
	errno = ENOSYS;
	return -1;
#endif
}
#endif

#ifdef WIN32

/*
 * AddUserToTokenDacl(HANDLE hToken)
 *
 * 此功能将当前用户帐户添加到受限
 * 令牌中，该令牌用于创建受限进程。
 *
 * 这是必要的，因为在 XP/2K3 和 Vista/2008 的修补程序中
 * 出现了一些安全更改。
 *
 * 在这些机器上，Administrator 账户不包含在
 * 默认 DACL 中 - 您只会得到 Administrators + System。对于
 * 普通用户，您会得到 User + System。因为我们在创建受限令牌时
 * 删除了 Administrators，所以 DACL 中只剩下 System
 * ，这会导致在以 Administrator 身份运行时，后续的 CreatePipe()
 * 和 CreateProcess() 调用出现访问被拒绝错误。
 *
 * 此函数通过修改进程将使用的令牌的 DACL
 * 来解决此问题，并显式重新添加当前
 * 用户帐户。这仍然是安全的，因为 Administrator 账户
 * 从 Administrators 组继承其权限 - 它没有自己的权限。
 */
BOOL
AddUserToTokenDacl(HANDLE hToken)
{
	int			i;
	ACL_SIZE_INFORMATION asi;
	ACCESS_ALLOWED_ACE *pace;
	DWORD		dwNewAclSize;
	DWORD		dwSize = 0;
	DWORD		dwTokenInfoLength = 0;
	PACL		pacl = NULL;
	PTOKEN_USER pTokenUser = NULL;
	TOKEN_DEFAULT_DACL tddNew;
	TOKEN_DEFAULT_DACL *ptdd = NULL;
	TOKEN_INFORMATION_CLASS tic = TokenDefaultDacl;
	BOOL		ret = FALSE;

	/* 确定 DACL 信息的缓冲区大小 */
	if (!GetTokenInformation(hToken, tic, (LPVOID) NULL, dwTokenInfoLength, &dwSize))
	{
		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		{
			ptdd = (TOKEN_DEFAULT_DACL *) LocalAlloc(LPTR, dwSize);
			if (ptdd == NULL)
			{
				log_error(errcode(ERRCODE_OUT_OF_MEMORY),
						  _("out of memory"));
				goto cleanup;
			}

			if (!GetTokenInformation(hToken, tic, (LPVOID) ptdd, dwSize, &dwSize))
			{
				log_error(errcode(ERRCODE_SYSTEM_ERROR),
						  "could not get token information: error code %lu",
						  GetLastError());
				goto cleanup;
			}
		}
		else
		{
			log_error(errcode(ERRCODE_SYSTEM_ERROR),
					  "could not get token information buffer size: error code %lu",
					  GetLastError());
			goto cleanup;
		}
	}

	/* 获取 ACL 信息 */
	if (!GetAclInformation(ptdd->DefaultDacl, (LPVOID) &asi,
						   (DWORD) sizeof(ACL_SIZE_INFORMATION),
						   AclSizeInformation))
	{
		log_error(errcode(ERRCODE_SYSTEM_ERROR),
				  "could not get ACL information: error code %lu",
				  GetLastError());
		goto cleanup;
	}

	/* 获取当前用户 SID */
	if (!GetTokenUser(hToken, &pTokenUser))
		goto cleanup;			/* 调用方打印了一个消息 */

	/* 确定新 ACL 的大小 */
	dwNewAclSize = asi.AclBytesInUse + sizeof(ACCESS_ALLOWED_ACE) +
		GetLengthSid(pTokenUser->User.Sid) - sizeof(DWORD);

	/* 分配 ACL 缓冲区并初始化它 */
	pacl = (PACL) LocalAlloc(LPTR, dwNewAclSize);
	if (pacl == NULL)
	{
		log_error(errcode(ERRCODE_OUT_OF_MEMORY),
				  _("out of memory"));
		goto cleanup;
	}

	if (!InitializeAcl(pacl, dwNewAclSize, ACL_REVISION))
	{
		log_error(errcode(ERRCODE_SYSTEM_ERROR),
				  "could not initialize ACL: error code %lu", GetLastError());
		goto cleanup;
	}

	/* 循环遍历现有 ACE，构建新的 ACL */
	for (i = 0; i < (int) asi.AceCount; i++)
	{
		if (!GetAce(ptdd->DefaultDacl, i, (LPVOID *) &pace))
		{
			log_error(errcode(ERRCODE_SYSTEM_ERROR),
					  "could not get ACE: error code %lu", GetLastError());
			goto cleanup;
		}

		if (!AddAce(pacl, ACL_REVISION, MAXDWORD, pace, ((PACE_HEADER) pace)->AceSize))
		{
			log_error(errcode(ERRCODE_SYSTEM_ERROR),
					  "could not add ACE: error code %lu", GetLastError());
			goto cleanup;
		}
	}

	/* 添加当前用户的新 ACE */
	if (!AddAccessAllowedAceEx(pacl, ACL_REVISION, OBJECT_INHERIT_ACE, GENERIC_ALL, pTokenUser->User.Sid))
	{
		log_error(errcode(ERRCODE_SYSTEM_ERROR),
				  "could not add access allowed ACE: error code %lu",
				  GetLastError());
		goto cleanup;
	}

	/* 在令牌中设置新的 DACL */
	tddNew.DefaultDacl = pacl;

	if (!SetTokenInformation(hToken, tic, (LPVOID) &tddNew, dwNewAclSize))
	{
		log_error(errcode(ERRCODE_SYSTEM_ERROR),
				  "could not set token information: error code %lu",
				  GetLastError());
		goto cleanup;
	}

	ret = TRUE;

cleanup:
	if (pTokenUser)
		LocalFree((HLOCAL) pTokenUser);

	if (pacl)
		LocalFree((HLOCAL) pacl);

	if (ptdd)
		LocalFree((HLOCAL) ptdd);

	return ret;
}

/*
 * GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser)
 *
 * 从进程令牌中获取用户的令牌信息。
 *
 * 此函数的调用者负责对
 * 返回的 TOKEN_USER 内存调用 LocalFree()。
 */
static BOOL
GetTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser)
{
	DWORD		dwLength;

	*ppTokenUser = NULL;

	if (!GetTokenInformation(hToken,
							 TokenUser,
							 NULL,
							 0,
							 &dwLength))
	{
		if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)
		{
			*ppTokenUser = (PTOKEN_USER) LocalAlloc(LPTR, dwLength);

			if (*ppTokenUser == NULL)
			{
				log_error(errcode(ERRCODE_OUT_OF_MEMORY),
						  _("out of memory"));
				return FALSE;
			}
		}
		else
		{
			log_error(errcode(ERRCODE_SYSTEM_ERROR),
					  "could not get token information buffer size: error code %lu",
					  GetLastError());
			return FALSE;
		}
	}

	if (!GetTokenInformation(hToken,
							 TokenUser,
							 *ppTokenUser,
							 dwLength,
							 &dwLength))
	{
		LocalFree(*ppTokenUser);
		*ppTokenUser = NULL;

		log_error(errcode(ERRCODE_SYSTEM_ERROR),
				  "could not get token information: error code %lu",
				  GetLastError());
		return FALSE;
	}

	/* *ppTokenUser 中的内存由调用者 LocalFree() */
	return TRUE;
}

#endif
